package com.life.ai.server.common.utils;

import com.life.ai.server.common.constants.CacheConstants;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.function.Function;

/**
 * Redis缓存工具(解决缓存穿透)
 * RedisCacheUtil
 *
 * @author kylewka
 * @since 2023/7/4
 */
@Slf4j
@Component
public class RedisCacheUtil {

    private final static String LOCK_PREFIX = "cache_lock:";

    private static RedissonClient  redissonClient;

    @Autowired
    public void setRedissonClient(RedissonClient redissonClient) {
        RedisCacheUtil.redissonClient = redissonClient;
    }

    /**
     * 获取加锁key
     */
    private static String getLockKey(String key) {
        return LOCK_PREFIX + key;
    }

    /**
     * 获取Redis缓存
     * 不存在则在互斥锁状态下重新放入缓存(不缓存空值)
     *
     * @param prefix   缓存前缀
     * @param id       id
     * @param function 执行方法
     * @param seconds  缓存时长(秒)
     */
    public static <ID, T> T query(String prefix, ID id, Function<ID, T> function, long seconds) {
        return query(prefix, id, function, seconds, false, null);
    }


    /**
     * 获取Redis缓存
     * 不存在则在互斥锁状态下重新放入缓存(缓存空值)
     *
     * @param prefix   缓存前缀
     * @param id       id
     * @param function 执行方法
     * @param seconds  缓存时长
     */
    public static <ID, T> T queryWithNullCache(String prefix, ID id, Function<ID, T> function, long seconds) {
        return query(prefix, id, function, seconds, true, null);
    }

    /**
     * 获取Redis缓存
     * 不存在则在互斥锁状态下重新放入缓存(缓存空值)
     *
     * @param prefix   缓存前缀
     * @param id       id
     * @param function 执行方法
     * @param seconds  缓存时长
     * @param nullTtl  空值过期时间
     */
    public static <ID, T> T queryWithNullCache(String prefix, ID id, Function<ID, T> function, long seconds, long nullTtl) {
        return query(prefix, id, function, seconds, true, nullTtl);
    }


    /**
     * 获取Redis缓存
     * 不存在则在互斥锁状态下重新放入缓存
     *
     * @param prefix    缓存前缀
     * @param id        id
     * @param function  执行方法
     * @param seconds   缓存时长
     * @param cacheNull 是否缓存空值
     */
    private static <ID, T> T query(String prefix, ID id, Function<ID, T> function, long seconds, boolean cacheNull, Long nullTtl) {
        String cacheKey = prefix + id;
        T result = RedisUtils.get(cacheKey);
        if (result != null) {
            return result;
        }
        // 添加互斥锁
        RLock lock = redissonClient.getLock(getLockKey(cacheKey));
        boolean locked = lock.tryLock();
        if (locked) {
            try {
                // 再次获取缓存
                result = RedisUtils.get(cacheKey);
                if (result != null) {
                    return result;
                }
                // 缓存中不存在查库
                T value = function.apply(id);
                if (value != null) {
                    RedisUtils.set(cacheKey, value, seconds);
                    result = value;
                }
                // 缓存不存在(缓存空值)
                if (cacheNull) {
                    long cacheNullTtl = nullTtl == null ? CacheConstants.CACHE_NULL_TTL : nullTtl;
                    RedisUtils.set(cacheKey, null, cacheNullTtl);
                }
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
        return result;
    }
}
